<?php
// This file is part of Preg question type - https://code.google.com/p/oasychev-moodle-plugins/
//
// Preg question type is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Unit tests for question/type/preg/tReger/...
 *
 * @package    qtype_preg
 * @copyright  2012 Oleg Sychev, Volgograd State Technical University
 * @author     Penskoy Nikita <nik95penik@yandex.ru>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

defined('MOODLE_INTERNAL') || die();

global $CFG;
require_once($CFG->dirroot . '/question/type/preg/treger/beforeparsing.php');
require_once($CFG->dirroot . '/question/type/poasquestion/jlex.php');
require_once($CFG->dirroot . '/question/type/preg/treger/lex.lex.php');
require_once($CFG->dirroot . '/question/type/preg/treger/bison.php');

class Tests extends PHPUnit_Framework_TestCase {
    public function do_lex($text, $results, $values) {
        file_put_contents("test.txt", $text);
        $scanner = new qtype_preg_lexer(fopen('test.txt', 'r'));
        $i = 0;
        $result = true;
        while ( $tok = $scanner->nextToken()) {
            if ( $tok->type !== $results[$i] || $tok->value !== $values[$i]) {
                $result = false;
            }
            $i++;
        }
        return $result;
    }

    public function do_parse($text) {
        $parser = new qtype_preg_parser();
        file_put_contents("test.txt", $text);
        $scanner = new qtype_preg_lexer(fopen('test.txt', 'r'));

        // $parser->PrintTrace();
        while (($token = $scanner->nextToken())) {
                $parser->doParse($token->type, $token->value);
        }
        $parser->doParse(0, null);
        return $parser->get_root();
    }

    public function test_check_description() {

        $this->assertTrue( '' == check_description('а'));
        // Odin simvol bez kavyichek.

        $this->assertTrue( '' == check_description('«а»'));
        // Odin simvol v kavyichkah.

        $this->assertTrue( '' == check_description('«арбуз»'));
        // Slovo v kavyichkah.

        $this->assertTrue( '' == check_description('«Мы ели арбуз.»'));
        // Predlozhenie v kavyichkah.

        $this->assertTrue( '' == check_description('«а\"»'));
        // Kavyichki v kavyichkah.

        $this->assertTrue( '' == check_description('«а\\»»'));
        // Uglovyie kavyichki v kavyichkah.

        $this->assertTrue( '' == check_description('«а\\\»'));
        // Zakryitie kavyichek posle 2-h sleshey.

        $this->assertTrue( '' == check_description('\\\«а»'));
        // Otkryitie kavyichek posle 2-h sleshey.

        $this->assertTrue( '' == check_description('"абруз" или "a""я"'));
        // Posledovatelnost kavyichek.

        $this->assertTrue( "incorrect open-quot at line = 1 pos = 5\n" == check_description('«а»б«в\\»'));
        // Nezakryitaya posledovatelnost kavyichek.

        $this->assertTrue( "incorrect close-quot at line = 1 pos = 7\n" == check_description('аб «в»»'));
        // Neotkryitaya, no zakryitaya kavyichka.

        $this->assertTrue( '' == check_description('(а)'));
        // Bukva v skobkah.

        $this->assertTrue( '' == check_description('(\")'));
        // Kavyichka v skobkah.

        $this->assertTrue( '' == check_description('("арбуз")'));
        // Slovo v kavyichkah v skobkah.

        $this->assertTrue( "incorrect open-bracket at line = 1 pos = 1\n" == check_description('(а'));
        // Nezakryitaya skobka.

        $this->assertTrue( "incorrect open-bracket at line = 1 pos = 1\nincorrect open-bracket at line = 1 pos = 2\n" == check_description('(((а)'));
        // Mnogo nezakryityih skobok.

        $this->assertTrue( "incorrect close-bracket at line = 1 pos = 2\n" == check_description('а)'));
        // Zakryivayuschaya skobka bez otkryivayuschey.

        $this->assertTrue( "incorrect close-bracket at line = 1 pos = 4\nincorrect close-bracket at line = 1 pos = 5\n" == check_description('(а)))'));
        // Mnogo zakryivayuschih skobok bez otkryivayuschih.

        $this->assertTrue( "incorrect open-bracket at line = 1 pos = 1\nincorrect open-quot at line = 1 pos = 2\n" == check_description('("а)'));
        // Nezakryitaya kavyichka v skobkah.

        $this->assertTrue( "incorrect open-bracket at line = 1 pos = 1\n" == check_description('("а"\\\\ \)'));
        // Nezakryitaya skobka, vnutri kotoroy kavyichki.

        $this->assertTrue( '' == check_description('(\\\\)'));
        // Slesh v skobkah.

        $this->assertTrue( '' == check_description('(а) (\)) ("абв")'));
        // Posledovatelnost skobok.

        $this->assertTrue( '' == check_description('("а)")'));
        // Skobka vnutri bukvalnoy posledovatelnosti.

        $this->assertTrue( '' == check_description('("а\)\")")'));
        // Skobka i kavyichka vnutri bukvalnoy posledovatelnosti.
    }

    public function test_set_all_state_patterns() {

        $this->assertTrue( 'а, б, в' == set_all_state_patterns('а, б, в'));
        // Net shablonov.

        $this->assertTrue( '«%%temp0» а' == set_all_state_patterns('%%temp0 а'));
        // Est shablon.

        $this->assertTrue( '%%temp а' == set_all_state_patterns('%%temp а'));
        // Est shablon bez nomera.

        $this->assertTrue( '%%template а' == set_all_state_patterns('%%template а'));
        // Est slovo, nachinayuscheesya s shablona.

        $this->assertTrue( '«%%temp10», «%%temp1». %%template01 , а' == set_all_state_patterns('%%temp10, %%temp1. %%template01 , а'));
        // Posledovatelnost shablonov.
    }

    public function test_mark_patterns_as_templates() {

        $templates = array();
        $this->assertTrue( 'а, б, в' == mark_patterns_as_Templates('а, б, в', $templates));
        // Net shablonov.

        $t1 = new Template('а', '%%temp0');
        $templates = array($t1);
        $this->assertTrue( '%%temp0, б, в' == mark_patterns_as_Templates('а, б, в', $templates));
        // Est shablon.

        $t2 = new Template('а б', '%%temp1');
        $templates = array($t2, $t1);
        $this->assertTrue( '%%temp1, с' == mark_patterns_as_Templates('а б, с', $templates));
        // Est shablon iz neskolkih slov.

        $t1 = new Template(', с', '%%temp0');
        $templates = array($t1, $t2);
        $d = mark_patterns_as_Templates('а б , с', $templates);
        $this->assertTrue( '%%temp1 %%temp0' == mark_patterns_as_Templates('а б , с', $templates));
        // Dva shablona podryad.

        $t1 = new Template('а б, с', '%%temp0');
        $t2 = new Template('б, с', '%%temp1');
        $templates = array($t1, $t2);
        $this->assertTrue( '%%temp0' == mark_patterns_as_Templates('а б, с', $templates));
        // Shablonyi s odinakovoy chastyu raznoy dlinyi.

        $t1 = new Template('а бв', '%%temp0');
        $t2 = new Template('с', '%%temp1');
        $templates = array($t1, $t2);
        $this->assertTrue( 'а бвг, %%temp1' == mark_patterns_as_Templates('а бвг, с', $templates));
        // Shablon menshe, chem slovo v opisanii.

        $t1 = new Template('%%', '%%temp1');
        $t2 = new Template('%%tem', '%%temp0');
        $templates = array($t1, $t2);
        $this->assertTrue( '%%temp0, "%%template", %%temp1' == mark_patterns_as_Templates('%%tem, "%%template", %%', $templates));
        // Shablon kak nachalo spets.znacheniya.

        $this->assertTrue( '"а", "б %%tem, c "' == mark_patterns_as_Templates('"а", "б %%tem, c "', $templates));
        // Znachenie v kavyichkah, sovpadayuschee s shablonom.

        $t1 = new Template('а', '%%temp0');
        $t2 = new Template('%%tem', '%%temp1');
        $templates = array($t1, $t2);
        $this->assertTrue( '\"%%temp0", ""б %%tem, c "' == mark_patterns_as_Templates('\"а", ""б %%tem, c "', $templates));
        // Znachenie posle ekranirovannoy kavyichki.
    }

    public function test_lexer() {

        $in = 'а б в';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::CONSTANT, qtype_preg_parser::CONSTANT);
        $values = array('а', 'б', 'в');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = 'слов нет';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::CONSTANT);
        $values = array('слов', 'нет');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = '«слово» "буква" "#$%"';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::CONSTANT, qtype_preg_parser::CONSTANT);
        $values = array('слово', 'буква', '#$%');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = '«много слов» "а а а"';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::CONSTANT);
        $values = array('много слов', 'а а а');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = 'слово дверь слово строка';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::CONSTANT);
        $values = array('дверь', 'строка');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = ' а «слово дверь» аб';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::CONSTANT, qtype_preg_parser::CONSTANT);
        $values = array('а', 'слово дверь', 'аб');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = 'буква л а';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::CONSTANT);
        $values = array('л', 'а');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = '«буква л» буква а';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::CONSTANT);
        $values = array('буква л', 'а');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = 'а-я б-в';
        $results = array(qtype_preg_parser::DIAPASON, qtype_preg_parser::DIAPASON);
        $values = array('а-я', 'б-в');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = 'з - н а-б';
        $results = array(qtype_preg_parser::DIAPASON, qtype_preg_parser::DIAPASON);
        $values = array('з-н', 'а-б');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = '0-3 0-1';
        $results = array(qtype_preg_parser::DIAPASON, qtype_preg_parser::DIAPASON);
        $values = array('0-3', '0-1');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = '0-к Мамин-Сибиряк';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::CONSTANT);
        $values = array('0-к', 'Мамин-Сибиряк');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = '%%temp2 %%temp123';
        $results = array(qtype_preg_parser::TEMPLATE, qtype_preg_parser::TEMPLATE);
        $values = array('2', '123');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = 'а, затем б';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::CONCAT,
                         qtype_preg_parser::CONCAT, qtype_preg_parser::CONSTANT);
        $values = array('а', null, null, 'б');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = 'а, потом б, потом с';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::CONCAT, qtype_preg_parser::CONCAT,
                         qtype_preg_parser::CONSTANT, qtype_preg_parser::CONCAT, qtype_preg_parser::CONCAT,
                         qtype_preg_parser::CONSTANT);
        $values = array('а', null, null, 'б', null, null, 'с');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = 'а. б.';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::CONCAT,
                         qtype_preg_parser::CONSTANT, qtype_preg_parser::CONCAT);
        $values = array('а', null, 'б', null);
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = 'а... б!';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::CONCAT,
                         qtype_preg_parser::CONSTANT, qtype_preg_parser::CONCAT);
        $values = array('а', null, 'б', null);
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = 'лап заканчивая а';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::ATEND, qtype_preg_parser::CONSTANT);
        $values = array('лап', null, 'а');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        $in = 'Начинается с а, затем б';
        $results = array(qtype_preg_parser::BEG, qtype_preg_parser::CONSTANT, qtype_preg_parser::CONCAT,
                         qtype_preg_parser::CONCAT, qtype_preg_parser::CONSTANT);
        $values = array(null, 'а', null, null, 'б');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 20. скобка
        $in = ' ( а, б)(в ) ';
        $results = array(qtype_preg_parser::LP, qtype_preg_parser::CONSTANT, qtype_preg_parser::CONCAT,
                           qtype_preg_parser::CONSTANT, qtype_preg_parser::RP,
                           qtype_preg_parser::LP, qtype_preg_parser::CONSTANT, qtype_preg_parser::RP);
        $values = array(null, 'а', null, 'б', null, null, 'в', null);
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 21. конструкция «может быть»
        $in = 'Может  быть а, б';
        $results = array(qtype_preg_parser::MAYBE, qtype_preg_parser::CONSTANT,
                         qtype_preg_parser::CONCAT, qtype_preg_parser::CONSTANT);
        $values = array(null, 'а', null, 'б');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 22. повторение с «не более»
        $in = 'а повторить не более 3 раз';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::TO);
        $values = array('а', '3');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 23. повторение с «до»
        $in = 'аб до 1 раза';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::TO);
        $values = array('аб', '1');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 24. повторение с «хотя бы»
        $in = 'аб повторить хотя бы 2 раза';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::FROM);
        $values = array('аб', '2');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 25. повторение с «от»
        $in = 'аб повтор от 1 раза';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::FROM);
        $values = array('аб', '1');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 26. повторение с «от» без дополнительных слов
        $in = 'аб от 1 раза';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::FROM);
        $values = array('аб', '1');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 27. повторение точного числа раз
        $in = 'аб повторять 55 раз';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::NUMBER);
        $values = array('аб', '55');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 28. повторение точного числа раз без дополнительных слов
        $in = 'аб 55 раз';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::NUMBER);
        $values = array('аб', '55');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 29. конструкция «то»
        $in = ', то б';
        $results = array(qtype_preg_parser::THEN, qtype_preg_parser::CONSTANT);
        $values = array(null, 'б');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 30. конструкция «иначе»
        $in = ', то б, в, иначе с';
        $results = array(qtype_preg_parser::THEN, qtype_preg_parser::CONSTANT,
                         qtype_preg_parser::CONCAT, qtype_preg_parser::CONSTANT,
                         qtype_preg_parser::ELSEM, qtype_preg_parser::CONSTANT);
        $values = array(null, 'б', null, 'в', null, 'с');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 31. конструкция «если»
        $in = 'Если было совпадение с группой f, то а, иначе б, с';
        $results = array(qtype_preg_parser::IFM, qtype_preg_parser::CONSTANT,
                         qtype_preg_parser::THEN, qtype_preg_parser::CONSTANT,
                         qtype_preg_parser::ELSEM, qtype_preg_parser::CONSTANT,
                         qtype_preg_parser::CONCAT, qtype_preg_parser::CONSTANT);
        $values = array(null, 'f', null, 'а', null, 'б', null, 'с');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 32. ссылка на совпавшую часть
        $in = 'то, что совпало с подмаской f';
        $results = array(qtype_preg_parser::FIND, qtype_preg_parser::CONSTANT);
        $values = array(null, 'f');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 33. объявление группы «группа»
        $in = 'группа f [а]';
        $results = array(qtype_preg_parser::GROUP, qtype_preg_parser::CONSTANT,
                    qtype_preg_parser::LB, qtype_preg_parser::CONSTANT, qtype_preg_parser::RB);
        $values = array(null, 'f', null, 'а', null);
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 34. объявление группы «подмаска»
        $in = 'а подмаска ft [а], б';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::GROUP, qtype_preg_parser::CONSTANT,
                         qtype_preg_parser::LB, qtype_preg_parser::CONSTANT,
                         qtype_preg_parser::RB, qtype_preg_parser::CONCAT, qtype_preg_parser::CONSTANT);
        $values = array('а', null, 'ft', null, 'а', null, null, 'б');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 35. конструкция «любой из»
        $in = 'Любой из(а, б)';
        $results = array(qtype_preg_parser::ANYOF, qtype_preg_parser::LP, qtype_preg_parser::CONSTANT,
                         qtype_preg_parser::CONCAT, qtype_preg_parser::CONSTANT, qtype_preg_parser::RP);
        $values = array(null, null, 'а', null, 'б', null);
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 36. конструкция «любой символ, кроме»
        $in = 'Любой символ, кроме б';
        $results = array(qtype_preg_parser::ANYEXCEPTOF, qtype_preg_parser::CONSTANT);
        $values = array(null, 'б');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 37. конструкция «один из»
        $in = 'Один из (а, б)';
        $results = array(qtype_preg_parser::ONEOF, qtype_preg_parser::LP, qtype_preg_parser::CONSTANT,
                         qtype_preg_parser::CONCAT, qtype_preg_parser::CONSTANT, qtype_preg_parser::RP);
        $values = array(null, null, 'а', null, 'б', null);
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 38. конструкция «или»
        $in = 'а или б';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::ALTERNATIVE, qtype_preg_parser::CONSTANT);
        $values = array('а', null, 'б');
        $this->assertTrue( $this->do_lex($in, $results, $values));

        // Тест 39. конструкция «или» с запятой
        $in = 'а, или б';
        $results = array(qtype_preg_parser::CONSTANT, qtype_preg_parser::ALTERNATIVE, qtype_preg_parser::CONSTANT);
        $values = array('а', null, 'б');
        $this->assertTrue( $this->do_lex($in, $results, $values));
    }

    public function test_convert_template() {

        // Тест 1. один узел
        $this->assertTrue( equal_trees(form_charset('а', qtype_preg_charset_flag::TYPE_SET, 'а'), convert_Template('а')));

        // Тест 2. конкатенация двух узлов
        $conc = new qtype_preg_node_concat();
        $conc->operands[] = form_charset('б', qtype_preg_charset_flag::TYPE_SET, 'б');
        $conc->operands[] = form_charset('а', qtype_preg_charset_flag::TYPE_SET, 'а');
        $this->assertTrue( equal_trees($conc, convert_Template('"б" а .2')));

        // Тест 3. альтернатива узла и конкатенации двух узлов
        $alt = new qtype_preg_node_alt();
        $alt->operands[] = form_charset('г', qtype_preg_charset_flag::TYPE_SET, 'г');
        $conc = new qtype_preg_node_concat();
        $conc->operands[] = form_charset('б', qtype_preg_charset_flag::TYPE_SET, 'б');
        $conc->operands[] = form_charset('а', qtype_preg_charset_flag::TYPE_SET, 'а');
        $alt->operands[] = $conc;
        $this->assertTrue( equal_trees($alt, convert_Template('г "б" а .2 |2')));

        // Тест 4. альтернатива трех частей, одна из которых повтор с одним дочерним узлом
        $alt = new qtype_preg_node_alt();
        $rep = form_quant('{2, 3}', false, 2, 3, false, true, false);
        $rep->operands[] = form_charset('е', qtype_preg_charset_flag::TYPE_SET, 'е');
        $alt->operands[] = $rep;
        $alt->operands[] = form_charset('г', qtype_preg_charset_flag::TYPE_SET, 'г');
        $conc = new qtype_preg_node_concat();
        $conc->operands[] = form_charset('б', qtype_preg_charset_flag::TYPE_SET, 'б');
        $conc->operands[] = form_charset('а', qtype_preg_charset_flag::TYPE_SET, 'а');
        $alt->operands[] = $conc;
        $this->assertTrue( equal_trees($alt, convert_Template('е {2,3}1 г "б" а .2 |3')));

        // Тест 5. конкатенация группы с одним дочерним узлом и узла
        $conc = new qtype_preg_node_concat();
        $group = form_named_subexpr('(?P<letter>...)', 'letter');
        $group->operands[] = form_charset('б', qtype_preg_charset_flag::TYPE_SET, 'б');
        $conc->operands[] = $group;
        $conc->operands[] = form_named_backref('\k<letter>', 3, '<', '>');
        $this->assertTrue( equal_trees($conc, convert_Template(' б (?P<letter>...)1 "\k<letter>" .2')));

        // Тест 6. Повторы и метасимволы
        $conc = new qtype_preg_node_concat();
        $rep = form_quant('+', true, 1, -1, false, true, false);
        $rep->operands[] = form_charset('а', qtype_preg_charset_flag::TYPE_SET, 'а');
        $conc->operands[] = $rep;
        $rep = form_quant('{,1}', false, 0, 1, false, true, false);
        $rep->operands[] = form_charset('а', qtype_preg_charset_flag::TYPE_SET, 'а');
        $conc->operands[] = $rep;
        $rep = form_quant('{1,}', true, 1, -1, false, true, false);
        $rep->operands[] = form_charset('а', qtype_preg_charset_flag::TYPE_SET, 'а');
        $conc->operands[] = $rep;
        $rep = form_quant('?', false, 0, 1, false, true, false);
        $rep->operands[] = form_charset('а', qtype_preg_charset_flag::TYPE_SET, 'а');
        $conc->operands[] = $rep;
        $rep = form_quant('*', true, 0, -1, false, true, false);
        $rep->operands[] = form_charset('а', qtype_preg_charset_flag::TYPE_SET, 'а');
        $conc->operands[] = $rep;
        $this->assertTrue( equal_trees($conc, convert_Template('а +1 а {,1}1 а {1,}1 а ?1 а *1 .5')));

        // Тест 7. Диапазоны
        $conc = new qtype_preg_node_concat();
        $conc->operands[] = form_charset('[a-z]', qtype_preg_charset_flag::TYPE_SET, '[a-z]');
        $conc->operands[] = form_charset('[a-c09!#]', qtype_preg_charset_flag::TYPE_SET, '[a-c09!#]');
        $conc->operands[] = form_charset('[\]\n]', qtype_preg_charset_flag::TYPE_SET, '[\]\n]');
        $this->assertTrue( equal_trees($conc, convert_Template('[a-z] [a-c09!#] [\]\n] .3')));

        // Тест 8. Утверждения
        $alt = new qtype_preg_node_alt();
        $alt->operands[] = new qtype_preg_leaf_assert_circumflex();
        $alt->operands[] = new qtype_preg_leaf_assert_esc_b(false);
        $alt->operands[] = form_charset('а', qtype_preg_charset_flag::TYPE_SET, 'а');
        $alt->operands[] = new qtype_preg_leaf_assert_esc_b(true);
        $rep = form_quant('?', false, 0, 1, false, true, false);
        $rep->operands[] = form_charset('\.', qtype_preg_charset_flag::TYPE_SET, '\.');
        $alt->operands[] = $rep;
        $alt->operands[] = new qtype_preg_leaf_assert_dollar();
        $this->assertTrue( equal_trees($alt, convert_Template('^ \b а \B \. ?1 $ |6')));
    }

    public function test_parse_simple_data() {

        // Тест 1. Один символ без кавычек
        $test = 'буква а';
        $result = convert_Template('а');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 2. Два символа без кавычек
        $test = 'аб';
        $result = convert_Template('а б .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 3. Длинное слово без кавычек
        $test = 'амбразура';
        $result = convert_Template('а м б р а з у р а .9');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 4. Последовательность не только из букв без кавычек
        $test = 'а+б';
        $result = convert_Template('а \+ б .3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 5. Один символ в кавычках
        $test = '«п»';
        $result = convert_Template('п');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 6. Два символа в кавычках
        $test = '«пб»';
        $result = convert_Template('п б .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 7. Слово в кавычках
        $test = '«просто»';
        $result = convert_Template('п р о с т о .6');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 8. Словосочетание в кавычках
        $test = '«просто слово»';
        $result = convert_Template('п р о с т о "[ ]" с л о в о .12');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 9. Много слов подряд
        $test = '«а а а а»';
        $result = convert_Template('а "[ ]" а "[ ]" а "[ ]" а .8');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 10. Диапазон без кавычек
        $test = 'а - з';
        $result = convert_Template('[а-з]');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 11. Диапазон в кавычках (значит просто строка)
        $test = '«а - з»';
        $result = convert_Template('а "[ ]" - "[ ]" з .5');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 12. Диапазон с помощью конструкции 'любой из' из 2-х элементов
        $test = 'любой из(а, б)';
        $result = convert_Template('[а-б]');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 13. Диапазон с помощью конструкции 'любой из' из многих элементов
        $test = 'любой из(а, б, в, г)';
        $result = convert_Template('[а-г]');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 14. Цифра в кавычках
        $test = '«1»';
        $result = convert_Template('"1"');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 15. Число в кавычках
        $test = '«123»';
        $result = convert_Template('"1" "2" "3" .3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 16. Диапазон из чисел
        $test = '0 - 9';
        $result = convert_Template('[0-9]');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 17 Диапазон из чисел в кавычках (значит просто строка)
        $test = '«0 - 9»';
        $result = convert_Template('"0" "[ ]" - "[ ]" "9" .5');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 18. Буквальная последовательность с буквами, цифрами и пробелами
        $test = '«1 слово2 2»';
        $result = convert_Template('"1" "[ ]" с л о в о "2" "[ ]" "2" .10');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 19. Буквальная последовательность с буквой между цифрами
        $test = '«11а1»';
        $result = convert_Template('"1" "1" а "1" .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 20. Тире
        $test = '-';
        $result = convert_Template('-');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 21. Пробел в кавычках
        $test = '" "';
        $result = convert_Template('"[ ]"');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 22. Пробел без кавычек
        $test = ' ';
        $result = null;
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 23. Диапазон из диапазонов через любой из
        $test = 'любой из(в-к, а-б)';
        $result = convert_Template('[а-к]');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 24. Диапазон любой из из 'любой из' и диапазона
        $test = 'любой из(любой из(а-б, в), з-к)';
        $result = convert_Template('[а-вз-к]');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 25. Пустой ввод
        $test = '';
        $result = null;
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));
    }

    public function test_parse_simple_concat_data() {

        // Тест 1. Конкатенация двух букв без кавычек
        $test = 'а, б';
        $result = convert_Template('а б .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 2. Конкатенация трех букв без кавычек
        $test = 'а, б, в';
        $result = convert_Template('а б в .3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 3. Конкатенация двух букв, первая в кавычках
        $test = '«а», б';
        $result = convert_Template('а б .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 4. Конкатенация буквы и слова в кавычках
        $test = 'а, «ббв»';
        $result = convert_Template('а б б в .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 5. Конкатенация двух слов в кавычках
        $test = '«аб», «блок»';
        $result = convert_Template('а б б л о к .6');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 6. Конкатенация слововочетания в кавычках и буквы
        $test = '«а б», в';
        $result = convert_Template('а "[ ]" б в .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 7. Конкатенация трех слов без кавычек
        $test = 'аб, в, где';
        $result = convert_Template('а б в г д е .6');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 8. Конкатенация слов в кавычках, с пробелом в одном из них и буквы
        $test = '«аб», затем    «где и», в';
        $result = convert_Template('а б г д е "[ ]" и в .8');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Конкатенация простых частей с группировкой
        // Тест 9. Буква без кавычек в скобках
        $test = '(а)';
        $result = convert_Template('а');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 10. Буква в кавычках в скобках
        $test = '(«а»)';
        $result = convert_Template('а');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 11. 2 буквы, одна из которых в кавычках, в скобках
        $test = '(«а», б)';
        $result = convert_Template('а б .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 12. Конкатенация слов в скобках и буквы
        $test = '(«а», «бв г»), д';
        $result = convert_Template('а б в "[ ]" г д .6');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 13. Конкатенация слов в скобках и слова
        $test = '(«а», «бв г»), де';
        $result = convert_Template('а б в "[ ]" г д е .7');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 14. Конкатенация слов в скобках и конкатенации букв
        $test = '(«а», «бв г»), д, е';
        $result = convert_Template('а б в "[ ]" г д е .7');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 15. Конкатенация скобок
        $test = '(а, б), (в, г)';
        $result = convert_Template('а б в г .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 16. Конкатенация скобок и слова
        $test = '(а, б), (в, г), де';
        $result = convert_Template('а б в г д е .6');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 17. Конкатенация скобки, слова и скобки
        $test = '(а, б), «вг», (д, е)';
        $result = convert_Template('а б в г д е .6');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 18. Конкатенация не явная
        $test = 'а арбуз';
        $result = convert_Template('а а р б у з .6');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 19. Конкатенация 'любой из' и буквы
        $test = 'любой из(а, б), в';
        $result = convert_Template('[а-б] в .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 20. дефис перед словом
        $test = 'аб -па';
        $result = convert_Template('а б - п а .6');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 21. дефис после слова
        $test = 'па- а';
        $result = convert_Template('п а - а .6');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 22. дефис внутри слова
        $test = 'а-па';
        $result = convert_Template('а - п а .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 23. дефис внутри большого слова
        $test = 'Мамин-Сибиряк';
        $result = convert_Template('М а м и н - С и б и р я к .13');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));
    }

    public function test_parse_simple_alt_data() {

        // Тест 1. Альтернатива двух букв без кавычек
        $test = 'а или б';
        $result = convert_Template('а б |2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 2. Альтернатива слова в кавычках и буквы
        $test = '«аб» или в';
        $result = convert_Template('а б .2 в |2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 3.  Альтернатива словосочетания в кавычках и буквы
        $test = '«аб в» или г';
        $result = convert_Template('а б "[ ]" в .4 г |2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 4. Альтернатива трех частей
        $test = '«а» или «б в», или г';
        $result = convert_Template('а б "[ ]" в .3 г |3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 5. Альтернатива множества частей
        $test = '«а», или «б в», или г или «д»';
        $result = convert_Template('а б "[ ]" в .3 г д |4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 6. Альтернатива 2 частей с помощью 'один из'
        $test = 'один из(«а» или б)';
        $result = convert_Template('а б |2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 7. Альтернатива 3 частей с помощью 'один из'
        $test = 'один из(«а» или б или «вг»)';
        $result = convert_Template('а б в г .2 |3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 8. Альтернатива множества частей с помощью 'один из'
        $test = 'один из(«а» или б или «вг» или (д или е))';
        $result = convert_Template('а б в г .2 д е |5');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // С группировкой
        // Тест 9. Альтернатива 2 частей в скобках
        $test = '(а или б)';
        $result = convert_Template('а б |2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 10. Альтернатива 2 частей в скобках и буквы
        $test = '(а или б) или в';
        $result = convert_Template('а б в |3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 11. Альтернатива 2 скобок с альтернативой
        $test = '(а или б) или(в или г)';
        $result = convert_Template('а б в г |4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 12. Альтернатива буквы и альтернативы в скобках
        $test = 'а или(б или в)';
        $result = convert_Template('а б в |3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 13. Альтернатива из 3 частей в скобках, где 3-я часть - альтернатива в скобках
        $test = '(абв или а или(б или в))';
        $result = convert_Template('а б в .3 а б в |4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 14. Альтернатива слова и вложенной альтернативы в скобках
        $test = 'абв или(а или(б или в))';
        $result = convert_Template('а б в .3 а б в |4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 15. Альтернатива частей в скобках
        $test = '(аб)или(вг) или(д или е)';
        $result = convert_Template('а б .2 в г .2 д е |4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 16. Альтернатива 'один из' странных последовательностей символов
        $test = 'один из( %% или "бв" или "!#$")';
        $result = convert_Template('% % .2 б в .2 ! # \$ .3 |3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));
    }

    public function test_parse_concat_alt_data() {

        // Тест 1. Конкатенация буквы и альтернативы
        $test = 'а, (б или в)';
        $result = convert_Template('а б в |2 .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 2. Конкатенация альтернативы и буквы
        $test = '(а или б), в';
        $result = convert_Template('а б |2 в .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 3. Конкатенация альтернатив
        $test = '(а или б), (в или г)';
        $result = convert_Template('а б |2 в г |2 .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 4. Конкатенация альтернатив и буквы
        $test = '(а или б или в), (г или д), е';
        $result = convert_Template('а б в |3 г д |2 е .3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 5. Конкатенация альтернатив из разных простых частей
        $test = '(а - з или б), (вгде или "бв")';
        $result = convert_Template('[а-з] б |2 в г д е .4 б в .2 |2 .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 6. Конкатенация простых частей и альтернативы
        $test = 'а, б, (в или г), д';
        $result = convert_Template('а б в г |2 д .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // с группировкой
        // Тест 7. Альтернатива конкатенации и буквы
        $test = 'а, б или в';
        $result = convert_Template('а б .2 в |2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 8. Конкатенация буквы и альтернативы в скобках
        $test = 'а, (б или в)';
        $result = convert_Template('а б в |2 .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 9. В скобках конкатенация буквы и альтернативы
        $test = '(а, (б или в))';
        $result = convert_Template('а б в |2 .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 10. Конкатенация скобок с альтернативой
        $test = '(а или б), (в или г)';
        $result = convert_Template('а б |2 в г |2 .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 11. Альтернатива буквы и последовательности в скобках
        $test = 'а или (б, в или г)';
        $result = convert_Template('а б в .2 г |3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 12. Альтернатива последовательности в скобках и буквы
        $test = '(а или б, в) или г';
        $result = convert_Template('а б в .2 г |3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 13. Альтернатива простых частей и скобок
        $test = 'а или (б, в) или г';
        $result = convert_Template('а б в .2 г |3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 14. Конкатенация один из и любой из
        $test = 'один из(а или б), в';
        $result = convert_Template('а б |2 в .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 15. Конкатенация любой из и один из
        $test = 'любой из (в, г), один из (а или б)';
        $result = convert_Template('[в-г] а б |2 .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));
    }

    public function test_parse_repeat_data() {

        // Тест 1. Повтор буквы
        $test = 'а 3 раза';
        $result = convert_Template('а {3}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 2. Повтор слова
        $test = 'аб 3 раза';
        $result = convert_Template('а б .2 {3}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 3. Повтор буквы в кавычках
        $test = '"а" 3 раза';
        $result = convert_Template('а {3}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 4. Повтор слова в кавычках
        $test = '"абв" 3 раза';
        $result = convert_Template('а б в .3 {3}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 5. Повтор слова в пределах раз
        $test = 'аб от 2 до 3 раз';
        $result = convert_Template('а б .2 {2,3}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 6. Повтор слова с бесконечной квантификацией
        $test = 'аб от 2 раз';
        $result = convert_Template('а б .2 {2,}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 7. Повтор без нижнего предела
        $test = 'аб до 3 раз';
        $result = convert_Template('а б .2 {,3}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 8. Повтор от какого-то числа раз
        $test = 'аб хотя бы 2 раза';
        $result = convert_Template('а б .2 {2,}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 9. Повтор с определением нижней границы как больше
        $test = 'аб более 2 раз';
        $result = convert_Template('а б .2 {3,}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 10. Повтор с определением вехней границы как меньше
        $test = 'аб менее 3 раз';
        $result = convert_Template('а б .2 {,2}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 11. Повтор от и до, нежадный
        $test = 'аб от 2 до 3 раз, наименьшее';
        $result = convert_Template('а б .2 {2,3}?1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 12. Повтор от, нежадный
        $test = 'аб от 2 раз, наименьшее';
        $result = convert_Template('а б .2 {2,}?1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 13. Повтор до, нежадный
        $test = 'аб до 2 раз, наименьшее';
        $result = convert_Template('а б .2 {,2}?1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 14. Повтор хотя бы, нежадный
        $test = 'аб хотя бы 2 раза, наименьшее';
        $result = convert_Template('а б .2 {2,}?1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 15. Повтор может быть с буквой
        $test = 'может быть а';
        $result = convert_Template('а ?1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 16. Повтор может быть и конкатенация
        $test = 'может быть а, б';
        $result = convert_Template('а ?1 б .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 17. Конкатенация и повтор может быть с словом
        $test = 'а, может быть бв';
        $result = convert_Template('а б в .2 ?1 .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 18. Конкатенация с повтором может быть
        $test = 'а, может быть «бвг», д';
        $result = convert_Template('а б в г .3 ?1 д .3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 19. Повтор может быть повтора может быть
        $test = 'Может быть может быть б';
        $result = convert_Template('б ?1 ?1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 20. Повтор может быть повтора буквы
        $test = 'Может быть б 3 раза';
        $result = convert_Template('б ?1 {3}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // с конкатенацией
        // Тест 21. Конкатенация буквы и повтора
        $test = 'а, б 3 раза';
        $result = convert_Template('а б {3}1 .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 22. Конкатенация букв и повтора
        $test = 'а, б 3 раза, в';
        $result = convert_Template('а б {3}1 в .3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 23. Конкатенация повторов и буквы
        $test = 'а 2 раза, б 3 раза, в';
        $result = convert_Template('а {2}1 б {3}1 в .3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 24. Конкатенация повторов от и до и до
        $test = 'а от 2 до 3 раз, б до 4 раз';
        $result = convert_Template('а {2,3}1 б {,4}1 .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // с альтернативой
        // Тест 25. Альтернатива буквы и повтора
        $test = 'а или б 3 раза';
        $result = convert_Template('а б {3}1 |2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 26.Конкатенация альтернативы буквы и повтора
        $test = 'а, (б или в 3 раза)';
        $result = convert_Template('а б в {3}1 |2 .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 27. Конкатенация альтернативы и повтора
        $test = '(а или б), в 3 раза';
        $result = convert_Template('а б |2 в {3}1 .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // С группировкой
        // Тест 28. Повтор буквы в скобках
        $test = '(а) 3 раза';
        $result = convert_Template('а {3}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 29. Повтор конкатенации в скобках
        $test = '(а, б) 3 раза';
        $result = convert_Template('а б .2 {3}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 30. Повтор повтора в скобках
        $test = '(а 3 раза) 2 раза';
        $result = convert_Template('а {3}1 {2}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 31. Повтор конкатенации буквы и повтора в скобках
        $test = '(а, б 2 раза) 3 раза';
        $result = convert_Template('а б {2}1 .2 {3}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 32. Повтор альтернативы в скобках
        $test = '(а или б) 3 раза';
        $result = convert_Template('а б |2 {3}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 33. Повтор сложного выражения в скобках
        $test = '((а или б 3 раза), в) от 2 до 3 раз';
        $result = convert_Template('а б {3}1 |2 в .2 {2,3}1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 34. Повтор как составная часть
        $test = '(а, б 2 раза) или б 3 раза';
        $result = convert_Template('а б {2}1 .2 б {3}1 |2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));
    }

    public function test_parse_groups_cond_data() {
        // Группы
        // Тест 1. Группа из буквы
        $test = 'группа b[а]';
        $result = convert_Template('а (?P<b>...)1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 2. Группа из буквы со строкой в названии
        $test = 'группа abc[а]';
        $result = convert_Template('а (?P<abc>...)1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 3. Группа из буквы со строкой в кавычках в названии
        $test = 'группа «abc»[а]';
        $result = convert_Template('а (?P<abc>...)1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 4. Группа из конкатенации
        $test = 'группа a[а, б]';
        $result = convert_Template('а б .2 (?P<a>...)1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 5. Группа из конкатенации буквы и повтора
        $test = 'группа a[а, бв 3 раза]';
        $result = convert_Template('а б в .2 {3}1 .2 (?P<a>...)1');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 6. Группа, ссылка на результаты группы
        $test = 'группа a[а, бв 3 раза], затем то, что совпало с группой а';
        $result = convert_Template('а б в .2 {3}1 .2 (?P<a>...)1 \k<а> .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 7. На несколько групп и совпадений
        $test = 'Группа name[а-я от 3 раз], = , группа number[0-9 хотя бы 1 раз],'.
                    ' затем то, что совпало с группой name, +, затем то, что совпало с группой number';
        $result = convert_Template('[а-я] {3,}1 (?P<name>...)1 = [0-9] {1,}1  (?P<number>...)1 \k<name> \+ \k<number> .6');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Условия
        // Тест 8. Группа и условие к ней
        $test = 'Группа letter [а-я], если было совпадение с группой letter, то б, иначе (в или г)';
        $result = convert_Template('[а-я] (?P<letter>...)1 б в г |2 (?(letter)...|...)2 .2');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 9. Конкатенация простой части и группы с условием
        $test = 'любой из(а, б).группа nm[ъ или х].если было совпадение с группой nm, то "1", иначе "2".';
        $result = convert_Template('[а-б] ъ х |2 (?P<nm>...)1 "1" "2" (?(nm)...|...)2 .3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 10.  Конкатенация группы с условием и простой части
        $test = 'группа nm[ъ или х].если было совпадение с группой nm, то "1", иначе "2". любой из(а, б).';
        $result = convert_Template('ъ х |2 (?P<nm>...)1 "1" "2" (?(nm)...|...)2 [а-б] .3');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));
    }

    public function test_parse_asserts_data() {

        // Тест 1. Сначала в начале описания
        $test = 'Сначала а, затем б, потом в';
        $result = convert_Template('^ а б в .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 2. 'в начале' после начала описания
        $test = 'а в начале, затем б, потом в';
        $result = convert_Template('^ а б в .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 3. 'в конце' в конце описания
        $test = 'а, затем б, в в конце';
        $result = convert_Template('а б в $ .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 4. 'в конце' перед концом описания
        $test = 'а, затем б, в конце в';
        $result = convert_Template('а б в $ .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 5. 'на границе слова' в начале
        $test = 'на границе слова а, затем б, в';
        $result = convert_Template('\\b а б в .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 6. 'на границе слова' в середине
        $test = 'а, на границе слова б, в';
        $result = convert_Template('а \\b б в .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 7. 'не на границе слова' в середине
        $test = 'а, нет границы слова б, в';
        $result = convert_Template('а \\B б в .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));

        // Тест 8. 'не на границе слова' в начале
        $test = 'нет границы слова а, б, в';
        $result = convert_Template('\\B а б в .4');
        $root = $this->do_parse($test);
        $this->assertTrue(equal_trees($root, $result));
    }
}

/*
        ob_start();
		var_dump($root);
		$output = ob_get_clean();
		file_put_contents('/srv/http/moodle/question/type/preg/treger/test_real.txt', $output);

		ob_start();
		var_dump($result);
		$output = ob_get_clean();
		file_put_contents('/srv/http/moodle/question/type/preg/treger/test_expect.txt', $output);
*/